package cn.iocoder.yudao.module.infrastructure.file.service;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infrastructure.file.controller.admin.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infrastructure.file.controller.admin.vo.file.FilePageReqVO;
import cn.iocoder.yudao.module.infrastructure.file.controller.admin.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.yudao.module.infrastructure.file.core.client.FileClient;
import cn.iocoder.yudao.module.infrastructure.file.core.client.s3.FilePresignedUrlRespDTO;
import cn.iocoder.yudao.module.infrastructure.file.core.utils.FileTypeUtils;
import cn.iocoder.yudao.module.infrastructure.file.dal.dataobject.FileDO;
import cn.iocoder.yudao.module.infrastructure.file.dal.mysql.FileMapper;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;

import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.api.infrastructure.ErrorCodeConstants.FILE_NOT_EXISTS;

/**
 * 文件 Service 实现类
 *
 * @author 芋道源码
 */
@Service
public class FileServiceImpl implements FileService {

    /**
     * 上传文件的前缀，是否包含日期（yyyyMMdd）
     * 目的：按照日期，进行分目录
     */
    static boolean PATH_PREFIX_DATE_ENABLE = true;

    @Resource
    private FileConfigService fileConfigService;

    @Resource
    private FileMapper fileMapper;

    @Override
    public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) {
        return fileMapper.selectPage(pageReqVO);
    }

    @Override
    @SneakyThrows // FIXED 重构
    public String createFile(String name, String path, byte[] content) {
        // 计算默认的 path 名
        String type = FileTypeUtils.getMineType(content, name);
        if (StrUtil.isEmpty(path)) {
            path = generateUploadPath(content, null);
        }
        // 如果 name 为空，则使用 path 填充
        if (StrUtil.isEmpty(name)) {
            name = path;
        }
        return doUpload(name, path, content, type);
    }

    @Override
    @SneakyThrows // FIXED 重构
    public String createFile(byte[] content, String name, String directory, String type) {
        // 1.1 处理 type 为空的情况
        if (StrUtil.isEmpty(type)) {
            type = FileTypeUtils.getMineType(content, name);
        }
        // 2.1 生成上传的 path，需要保证唯一
        String path = generateUploadPath(content, directory);
        return doUpload(name, path, content, type);
    }

    private String doUpload(String name, String path, byte[] content, String type) throws Exception {
        path = path.replaceAll("\\\\", "/");
        if (!path.contains("/")) {
            // 通过 MIME 进行文件夹归类
            path = type.split("/")[0] + "/" + path;
        }
        // 上传到文件存储器
        FileClient client = fileConfigService.getMasterFileClient();
        Assert.notNull(client, "客户端(master) 不能为空");
        String url = client.upload(content, path, type);
        // 保存到数据库
        fileMapper.insert(new FileDO().setConfigId(client.getId())
                .setName(name).setPath(path).setUrl(url)
                .setType(type).setSize(content.length));
        return url;
    }

    @VisibleForTesting
    String generateUploadPath(@Nonnull byte[] content, @Nullable String directory) {
        // 通过内容摘要生成文件路径进行文件复用，支持所谓的“秒传”
        String sha256Hex = DigestUtil.sha256Hex(content);
        // 基于 content 计算后缀
        String name = sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
        return generateUploadPath(name, directory, false);
    }

    @VisibleForTesting
    String generateUploadPath(@Nonnull String name, @Nullable String directory, boolean suffixTimestampEnable) {
        // 生成前缀、后缀
        String prefix = null;
        if (PATH_PREFIX_DATE_ENABLE) {
            prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN);
        }
        // 上传文件的后缀，是否包含时间戳，用于保证文件的唯一性，避免覆盖
        String suffix = null;
        if (suffixTimestampEnable) {
            suffix = String.valueOf(System.currentTimeMillis());
        }
        // 先拼接 suffix 后缀
        if (StrUtil.isNotEmpty(suffix)) {
            String ext = FileUtil.extName(name);
            if (StrUtil.isNotEmpty(ext)) {
                name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext;
            } else {
                name = name + StrUtil.C_UNDERLINE + suffix;
            }
        }
        // 再拼接 prefix 前缀
        if (StrUtil.isNotEmpty(prefix)) {
            name = prefix + StrUtil.SLASH + name;
        }
        // 最后拼接 directory 目录
        if (StrUtil.isNotBlank(directory)) {
            name = directory + StrUtil.SLASH + name;
        }
        return name;
    }

    @Override
    @SneakyThrows
    public FilePresignedUrlRespVO getFilePresignedUrl(String path) {
        FileClient fileClient = fileConfigService.getMasterFileClient();
        FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
        return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
                object -> object.setConfigId(fileClient.getId()));
    }

    @Override
    @SneakyThrows
    public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {
        // 1. 生成上传的 path，需要保证唯一
        String path = generateUploadPath(name, directory, true);
        // 2. 获取文件预签名地址
        return getFilePresignedUrl(path);
    }

    @Override
    public Long createFile(FileCreateReqVO createReqVO) {
        FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
        fileMapper.insert(file);
        return file.getId();
    }

    @Override
    public void deleteFile(Long id) throws Exception {
        // 校验存在
        FileDO file = validateFileExists(id);

        // 从文件存储器中删除
        FileClient client = fileConfigService.getFileClient(file.getConfigId());
        Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId());
        client.delete(file.getPath());

        // 删除记录
        fileMapper.deleteById(id);
    }

    private FileDO validateFileExists(Long id) {
        FileDO fileDO = fileMapper.selectById(id);
        if (fileDO == null) {
            throw exception(FILE_NOT_EXISTS);
        }
        return fileDO;
    }

    @Override
    public byte[] getFileContent(Long configId, String path) throws Exception {
        FileClient client = fileConfigService.getFileClient(configId);
        Assert.notNull(client, "客户端({}) 不能为空", configId);
        return client.getContent(path);
    }

    @Override // FIXME 新增
    public FileDO getFileByPath(String path) {
        return fileMapper.selectOne(FileDO::getPath, path);
    }

}
